use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::fmt;
use std::error::Error;
use std::str::FromStr;

#[derive(Debug)]
#[derive(Clone)]
pub struct Konfidenzmatrix<T: fmt::Debug> {
    name: String,
    true_positive: T,
    true_negative: T,
    false_positive: T,
    false_negative: T,
}

const STR_TRUE_POSITIVE: &str = "true_positive";
const STR_TRUE_NEGATIVE: &str = "true_negative";
const STR_FALSE_POSITIVE: &str = "false_positive";
const STR_FALSE_NEGATIVE: &str = "false_negative";
const STR_NAME: &str = "name";
const VAL_DEFAULT_NAME: &str = "standardmatrix";

#[derive(Debug)]
pub struct ParseKonfidenzmatrixError {
    description: String,
}

impl Error for ParseKonfidenzmatrixError {}

impl fmt::Display for ParseKonfidenzmatrixError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {     
        write!(f,
               "Invalid format for Konfidenzmatrix instances: {} ",
               self.description)
    }
}

impl Konfidenzmatrix<f32> {
    pub fn bedingte_wahrscheinlichkeiten(&self, tatsaechlich: bool) -> Vec<(bool, f32)> {
        if tatsaechlich == true {
            let sum: f32 = self.true_positive + self.false_negative;
            vec![ (true, self.true_positive / sum), (false, self.false_negative / sum) ]                   
        } else {
            let sum: f32 = self.true_negative + self.false_positive;
            vec![ (false, self.true_negative / sum), (true, self.false_positive / sum) ]
        }
    }
}


impl std::str::FromStr for Konfidenzmatrix<f32> {
    type Err = ParseKonfidenzmatrixError;   
    fn from_str(input: &str) -> Result<Self, Self::Err> {
        let mut data = HashMap::with_capacity(5);
        for part in input.split(",") {
            let subparts: Vec<&str> = part.split("=").collect();
            if subparts.len() < 2 {
                continue;
            } else {
                data.insert(String::from(subparts[0].trim()), String::from(subparts[1].trim()));
            }            
        }

        if ! datenmap_ist_komplett(&data) {
            Err(ParseKonfidenzmatrixError { description: String::from("Es fehlen Werte."), })
        } else {
            let r_prob_map = parse_floats(&data);
            match r_prob_map {
                Err(_k) => {
                    Err(ParseKonfidenzmatrixError
                        {
                            description: format!("Ungültige Wahrscheinlichkeiten: {}", _k),
                        }
                    )
                },
                Ok(prob_map) => {
                    if sind_wahrscheinlichkeiten(&prob_map) {
                        Ok(Konfidenzmatrix {
                            name: match data.get(STR_NAME) {
                                Some(x) => { x.to_string() },
                                None => { VAL_DEFAULT_NAME.to_string() },
                            },
                            true_positive: *prob_map.get(STR_TRUE_POSITIVE).unwrap(),
                            true_negative: *prob_map.get(STR_TRUE_NEGATIVE).unwrap(),
                            false_positive: *prob_map.get(STR_FALSE_POSITIVE).unwrap(),
                            false_negative: *prob_map.get(STR_FALSE_NEGATIVE).unwrap(),
                        })
                    } else {
                        Err(ParseKonfidenzmatrixError
                            {
                                description: String::from("Ungültige Wahrscheinlichkeiten."),
                            }
                        )
                    }
                }
            }
        }      
    }   
}

fn sind_wahrscheinlichkeiten(fltmap: &HashMap<String, f32>) -> bool {
    let mut sum: f32 = 0f32;
    let mut kleiner_oder_gl_eins: bool = true;
    for (_k, v) in fltmap.iter() {
        sum += v;
        if v > &1f32 {
            kleiner_oder_gl_eins = false;
        }
    }
    (sum - 1f32).abs() <= f32::EPSILON && kleiner_oder_gl_eins
}

fn parse_floats(datenmap: &HashMap<String, String>) -> Result<HashMap<String, f32>, Box<dyn Error>> {
    let mut rueck: HashMap<String, f32> = HashMap::with_capacity(4);
    for (k, v) in datenmap.iter() {
        if *k == STR_NAME {
            continue
        } else {
            let konv_wert = f32::from_str(v);
            if konv_wert.is_ok() {
                match konv_wert.clone() {
                    Err(k) => { return Err(Box::new( k )); },
                    Ok(f) => { rueck.insert(k.to_string(), f); }
                }
            }

        }
    }
    Ok(rueck)
}

fn datenmap_ist_komplett(datenmap: &HashMap<String, String>) -> bool {
    datenmap.contains_key(STR_FALSE_NEGATIVE)
        && datenmap.contains_key(STR_FALSE_POSITIVE)
        && datenmap.contains_key(STR_TRUE_NEGATIVE)
        && datenmap.contains_key(STR_TRUE_POSITIVE)
}

pub fn lese_matrix_datei(dateiname: String) -> Result<Konfidenzmatrix<f32>, &'static str> {
    let mut datei = match File::open(dateiname) {
        Ok(k) => { k },
        Err(_n) => {
            return Err("Fehler beim Öffnen der Datei");
        }
    };
    
    let mut inhalt = String::new();
    match datei.read_to_string(&mut inhalt) {
        Err(_m) => {
            Err("Fehler beim Lesen der Datei")
        },
        Ok(_n) => {
            match Konfidenzmatrix::from_str(&inhalt) {
                Ok(km) => Ok(km),
                Err(n) => { println!("Parserfehler: {}", n); Err("Parserfehler!") }
            }
        },
    }
    
}
